#ifndef COMMON_CLASSLOADER_H_
#define COMMON_CLASSLOADER_H_

#include <fermat/common/meta_object.h>
#include <fermat/common/manifest.h>
#include <fermat/common/shared_library.h>
#include <fermat/common/mutex.h>
#include <fermat/common/scoped_lock.h>
#include <map>
#include <memory>

namespace fermat {


template <class Base>
class ClassLoader {
public:
	typedef AbstractMetaObject<Base> Meta;
	typedef std::shared_ptr<Meta> MetaPtr;
	typedef Manifest<Base> Manif;
	typedef std::shared_ptr<Manif>  ManifPtr;
	typedef void (*InitializeLibraryFunc)();
	typedef void (*UninitializeLibraryFunc)();
	typedef bool (*BuildManifestFunc)(ManifestBase*);
	typedef std::shared_ptr<SharedLibrary> SharedLibraryPtr;

	struct LibraryInfo
	{
		SharedLibraryPtr pLibrary;
		ManifPtr         pManifest;
		int              refCount;
	};
	typedef std::map<std::string, LibraryInfo> LibraryMap;

	class Iterator {
	public:
		typedef std::pair<std::string, ManifPtr> Pair;

		Iterator(const typename LibraryMap::const_iterator& it)
		{
			_it = it;
		}
		Iterator(const Iterator& it)
		{
			_it = it._it;
		}
		~Iterator()
		{
		}
		Iterator& operator = (const Iterator& it)
		{
			_it = it._it;
			return *this;
		}
		inline bool operator == (const Iterator& it) const
		{
			return _it == it._it;
		}
		inline bool operator != (const Iterator& it) const
		{
			return _it != it._it;
		}
		Iterator& operator ++ () // prefix
		{
			++_it;
			return *this;
		}
		Iterator operator ++ (int) // postfix
		{
			Iterator result(_it);
			++_it;
			return result;
		}
		inline const Pair* operator * () const
		{
			_pair.first  = _it->first;
			_pair.second = _it->second.pManifest;
			return &_pair;
		}
		inline const Pair* operator -> () const
		{
			_pair.first  = _it->first;
			_pair.second = _it->second.pManifest;
			return &_pair;
		}

	private:
		typename LibraryMap::const_iterator _it;
		mutable Pair _pair;
	};

	ClassLoader()
	{
	}

	virtual ~ClassLoader()
	{

	}

	bool loadLibrary(const std::string& path, const std::string& manifest)
	{
		ScopedMutex lock(&_mutex);

		typename LibraryMap::iterator it = _map.find(path);
		if (it == _map.end()) {
			LibraryInfo li;
			li.pLibrary.reset(new SharedLibrary(path));
			li.pManifest.reset(new Manif());
			li.refCount  = 1;

			std::string fermatBuildManifestSymbol("fermatBuildManifest");
			fermatBuildManifestSymbol.append(manifest);
			if (li.pLibrary->hasSymbol("fermatInitializeLibrary")) {
				InitializeLibraryFunc initializeLibrary = (InitializeLibraryFunc) li.pLibrary->getSymbol("fermatInitializeLibrary");
				initializeLibrary();
			}
			if (li.pLibrary->hasSymbol(fermatBuildManifestSymbol)) {
				BuildManifestFunc buildManifest = (BuildManifestFunc) li.pLibrary->getSymbol(fermatBuildManifestSymbol);
				if (buildManifest(const_cast<Manif*>(li.pManifest.get()))) {
					_map[path] = li;
				} else {
					return false;
				}
			} else {
					return false;
			}
		} else {
			++it->second.refCount;
		}
		return true;
	}

	bool loadLibrary(const std::string& path)
	{
		return loadLibrary(path, "");
	}
		
	void unloadLibrary(const std::string& path)
	{
		ScopedMutex lock(&_mutex);

		typename LibraryMap::iterator it = _map.find(path);
		if (it != _map.end()) {
			if (--it->second.refCount == 0) {
				if (it->second.pLibrary->hasSymbol("fermatUninitializeLibrary")) {
					UninitializeLibraryFunc uninitializeLibrary = (UninitializeLibraryFunc) it->second.pLibrary->getSymbol("fermatUninitializeLibrary");
					uninitializeLibrary();
				}
				it->second.pLibrary->unload();
				_map.erase(it);
			}
		}

	}

	const MetaPtr findClass(const std::string& className) const
	{
		ScopedMutex lock(&_mutex);
		MetaPtr p;
		for (typename LibraryMap::const_iterator it = _map.begin(); it != _map.end(); ++it)
		{
			const ManifPtr pManif = it->second.pManifest;
			typename Manif::Iterator itm = pManif->find(className);
			if (itm != pManif->end()) {
				 p =  *itm;
			}
				
		}
		return p;
	}
	
	Base* create(const std::string& className) const
	{
		const MetaPtr pMeta = findClass(className);
		if (pMeta) {
			return pMeta->create();
		}
		return NULL;
	}
	
	Base* instance(const std::string& className) const
	{
		const MetaPtr pMeta = findClass(className);
		if (pMeta) {
			return pMeta->instance();
		}
		return NULL;
	}
	
	bool canCreate(const std::string& className) const
	{
		const MetaPtr pMeta = findClass(className);
		if (pMeta) {
			return pMeta->canCreate();
		}
		return NULL;
	}

	void destroy(const std::string& className, Base* pObject) const
	{
		const MetaPtr pMeta = findClass(className);
		if (pMeta) {
			pMeta->destroy();
		}

	}

	bool isAutoDelete(const std::string& className, Base* pObject) const
	{
		const MetaPtr pMeta = findClass(className);
		if (pMeta) {
			return pMeta->isAutoDelete();
		}
		return false;
	}
	
	const ManifPtr findManifest(const std::string& path) const
	{
		ScopedMutex lock(&_mutex);

		typename LibraryMap::const_iterator it = _map.find(path);
		if (it != _map.end())
			return it->second.pManifest;
		else
			return ManifPtr();
	}
	

	bool isLibraryLoaded(const std::string& path) const
	{
		return findManifest(path) != ManifPtr();
	}

	Iterator begin() const
	{
		ScopedMutex lock(&_mutex);

		return Iterator(_map.begin());
	}

	Iterator end() const
	{
		ScopedMutex lock(&_mutex);

		return Iterator(_map.end());
	}

private:
	LibraryMap _map;
	mutable Mutex _mutex;
};


} 
#endif